<?php
// +----------------------------------------------------------------------
// | TOPThink [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2010 http://topthink.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// $Id: MongoModel.class.php 2576 2012-01-12 15:09:01Z liu21st $


/**
 +------------------------------------------------------------------------------
 * TOPThink MongoModel模型类
 * 实现了ODM和ActiveRecords模式
 +------------------------------------------------------------------------------
 * @category   Think
 * @package  Think
 * @subpackage  Core
 * @author    liu21st <liu21st@gmail.com>
 * @version   $Id: MongoModel.class.php 2576 2012-01-12 15:09:01Z liu21st $
 +------------------------------------------------------------------------------
 */
class MongoModel extends Model {
	// 主键类型
	const TYPE_OBJECT = 1;
	const TYPE_INT = 2;
	const TYPE_STRING = 3;
	
	// 主键名称
	protected $pk = '_id';
	// _id 类型 1 Object 采用MongoId对象 2 Int 整形 支持自动增长 3 String 字符串Hash
	protected $_idType = self::TYPE_OBJECT;
	// 主键是否自动增长 支持Int型主键
	protected $_autoInc = false;
	// Mongo默认关闭字段检测 可以动态追加字段
	protected $autoCheckFields = false;
	
	/**
     +----------------------------------------------------------
	 * 利用__call方法实现一些特殊的Model方法
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param string $method 方法名称
	 * @param array $args 调用参数
     +----------------------------------------------------------
	 * @return mixed
     +----------------------------------------------------------
	 */
	public function __call($method, $args) {
		if (in_array ( strtolower ( $method ), array ('table', 'where', 'order', 'limit', 'page' ), true )) {
			// 连贯操作的实现
			$this->options [strtolower ( $method )] = $args [0];
			return $this;
		} elseif (strtolower ( substr ( $method, 0, 5 ) ) == 'getby') {
			// 根据某个字段获取记录
			$field = parse_name ( substr ( $method, 5 ) );
			$where [$field] = $args [0];
			return $this->where ( $where )->find ();
		} elseif (strtolower ( substr ( $method, 0, 10 ) ) == 'getfieldby') {
			// 根据某个字段获取记录的某个值
			$name = parse_name ( substr ( $method, 10 ) );
			$where [$name] = $args [0];
			return $this->where ( $where )->getField ( $args [1] );
		} else {
			throw_exception ( __CLASS__ . ':' . $method . L ( '_METHOD_NOT_EXIST_' ) );
			return;
		}
	}
	
	/**
     +----------------------------------------------------------
	 * 获取字段信息并缓存 主键和自增信息直接配置
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @return void
     +----------------------------------------------------------
	 */
	public function flush() {
		// 缓存不存在则查询数据表信息
		$fields = $this->db->getFields ();
		if (! $fields) { // 暂时没有数据无法获取字段信息 下次查询
			return false;
		}
		$this->fields = array_keys ( $fields );
		$this->fields ['_pk'] = $this->pk;
		$this->fields ['_autoinc'] = $this->_autoInc;
		foreach ( $fields as $key => $val ) {
			// 记录字段类型
			$type [$key] = $val ['type'];
		}
		// 记录字段类型信息
		if (C ( 'DB_FIELDTYPE_CHECK' ))
			$this->fields ['_type'] = $type;
		
		// 2008-3-7 增加缓存开关控制
		if (C ( 'DB_FIELDS_CACHE' )) {
			// 永久缓存数据表信息
			$db = $this->dbName ? $this->dbName : C ( 'DB_NAME' );
			F ( '_fields/' . $db . '.' . $this->name, $this->fields );
		}
	}
	
	// 写入数据前的回调方法 包括新增和更新
	protected function _before_write(&$data) {
		$pk = $this->getPk ();
		// 根据主键类型处理主键数据
		if (isset ( $data [$pk] ) && $this->_idType == self::TYPE_OBJECT) {
			$data [$pk] = new MongoId ( $data [$pk] );
		}
	}
	
	/**
     +----------------------------------------------------------
	 * count统计 配合where连贯操作
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @return integer
     +----------------------------------------------------------
	 */
	public function count() {
		// 分析表达式
		$options = $this->_parseOptions ();
		return $this->db->count ( $options );
	}
	
	/**
     +----------------------------------------------------------
	 * 获取下一ID 用于自动增长型
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param string $pk 字段名 默认为主键
     +----------------------------------------------------------
	 * @return mixed
     +----------------------------------------------------------
	 */
	public function getMongoNextId($pk = '') {
		if (empty ( $pk )) {
			$pk = $this->getPk ();
		}
		return $this->db->mongo_next_id ( $pk );
	}
	
	// 插入数据前的回调方法
	protected function _before_insert(&$data, $options) {
		// 写入数据到数据库
		if ($this->_autoInc && $this->_idType == self::TYPE_INT) { // 主键自动增长
			$pk = $this->getPk ();
			if (! isset ( $data [$pk] )) {
				$data [$pk] = $this->db->mongo_next_id ( $pk );
			}
		}
	}
	
	public function clear() {
		return $this->db->clear ();
	}
	
	// 查询成功后的回调方法
	protected function _after_select(&$resultSet, $options) {
		array_walk ( $resultSet, array ($this, 'checkMongoId' ) );
	}
	
	/**
     +----------------------------------------------------------
	 * 获取MongoId
     +----------------------------------------------------------
	 * @access protected
     +----------------------------------------------------------
	 * @param array $result 返回数据
     +----------------------------------------------------------
	 * @return array
     +----------------------------------------------------------
	 */
	protected function checkMongoId(&$result) {
		if (is_object ( $result ['_id'] )) {
			$result ['_id'] = $result ['_id']->__toString ();
		}
		return $result;
	}
	
	// 表达式过滤回调方法
	protected function _options_filter(&$options) {
		$id = $this->getPk ();
		if (isset ( $options ['where'] [$id] ) && $this->_idType == self::TYPE_OBJECT) {
			$options ['where'] [$id] = new MongoId ( $options ['where'] [$id] );
		}
	}
	
	/**
     +----------------------------------------------------------
	 * 查询数据
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param mixed $options 表达式参数
     +----------------------------------------------------------
	 * @return mixed
     +----------------------------------------------------------
	 */
	public function find($options = array()) {
		if (is_numeric ( $options ) || is_string ( $options )) {
			$id = $this->getPk ();
			$where [$id] = $options;
			$options = array ();
			$options ['where'] = $where;
		}
		// 分析表达式
		$options = $this->_parseOptions ( $options );
		$result = $this->db->find ( $options );
		if (false === $result) {
			return false;
		}
		if (empty ( $result )) { // 查询结果为空
			return null;
		} else {
			$this->checkMongoId ( $result );
		}
		$this->data = $result;
		$this->_after_find ( $this->data, $options );
		return $this->data;
	}
	
	/**
     +----------------------------------------------------------
	 * 字段值增长
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param string $field  字段名
	 * @param integer $step  增长值
     +----------------------------------------------------------
	 * @return boolean
     +----------------------------------------------------------
	 */
	public function setInc($field, $step = 1) {
		return $this->setField ( $field, array ('inc', $step ) );
	}
	
	/**
     +----------------------------------------------------------
	 * 字段值减少
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param string $field  字段名
	 * @param integer $step  减少值
     +----------------------------------------------------------
	 * @return boolean
     +----------------------------------------------------------
	 */
	public function setDec($field, $step = 1) {
		return $this->setField ( $field, array ('inc', '-' . $step ) );
	}
	
	/**
     +----------------------------------------------------------
	 * 获取一条记录的某个字段值
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param string $field  字段名
	 * @param string $spea  字段数据间隔符号
     +----------------------------------------------------------
	 * @return mixed
     +----------------------------------------------------------
	 */
	public function getField($field, $sepa = null) {
		$options ['field'] = $field;
		$options = $this->_parseOptions ( $options );
		if (strpos ( $field, ',' )) { // 多字段
			$resultSet = $this->db->select ( $options );
			if (! empty ( $resultSet )) {
				$_field = explode ( ',', $field );
				$field = array_keys ( $resultSet [0] );
				$move = $_field [0] == $_field [1] ? false : true;
				$key = array_shift ( $field );
				$key2 = array_shift ( $field );
				$cols = array ();
				$count = count ( $_field );
				foreach ( $resultSet as $result ) {
					$name = $result [$key];
					if ($move) { // 删除键值记录
						unset ( $result [$key] );
					}
					if (2 == $count) {
						$cols [$name] = $result [$key2];
					} else {
						$cols [$name] = is_null ( $sepa ) ? $result : implode ( $sepa, $result );
					}
				}
				return $cols;
			}
		} else { // 查找一条记录
			$result = $this->db->find ( $options );
			if (! empty ( $result )) {
				return $result [$field];
			}
		}
		return null;
	}
	
	/**
     +----------------------------------------------------------
	 * 执行Mongo指令
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param array $command  指令
     +----------------------------------------------------------
	 * @return mixed
     +----------------------------------------------------------
	 */
	public function command($command) {
		return $this->db->command ( $command );
	}
	
	/**
     +----------------------------------------------------------
	 * 执行MongoCode
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param string $code  MongoCode
	 * @param array $args   参数
     +----------------------------------------------------------
	 * @return mixed
     +----------------------------------------------------------
	 */
	public function mongoCode($code, $args = array()) {
		return $this->db->execute ( $code, $args );
	}
	
	// 数据库切换后回调方法
	protected function _after_db() {
		// 切换Collection
		$this->db->switchCollection ( $this->getTableName (), $this->dbName ? $this->dbName : C ( 'db_name' ) );
	}
	
	/**
     +----------------------------------------------------------
	 * 得到完整的数据表名 Mongo表名不带dbName
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @return string
     +----------------------------------------------------------
	 */
	public function getTableName() {
		if (empty ( $this->trueTableName )) {
			$tableName = ! empty ( $this->tablePrefix ) ? $this->tablePrefix : '';
			if (! empty ( $this->tableName )) {
				$tableName .= $this->tableName;
			} else {
				$tableName .= parse_name ( $this->name );
			}
			$this->trueTableName = strtolower ( $tableName );
		}
		return $this->trueTableName;
	}
}